using System;
using System.Xml;
using EnvDTE;
using System.IO;
using System.Collections;
using System.Windows.Forms;

namespace NDocBuilder
{
	/// <summary>
	/// This class represents a ndoc project file.
	/// </summary>
	/// <remarks>
	/// The <see cref="Builder"/> class will create a <see cref="NDocBuilder"/> instance for every project that is selected in the Visual Studio environment. 
	/// For every referenced project the <see cref="NDocProject"/> will create a <see cref="NDocAssembly"/> instance and adds it to the <see cref="Assemblies"/> collection.
	/// <seealso cref="Builder"/>
	/// <seealso cref="Build"/>
	/// </remarks>
	public class NDocProject
	{
		#region Constructor

		/// <summary>
		/// Constructor for NDocProject
		/// </summary>
		/// <param name="activeProject">The selected project</param>
		public NDocProject(Project activeProject)
		{
			m_ActiveProject = activeProject;
			
			OutputPane.OutputString("\nCurrent project: \"" + ActiveProject.Name + "\"\n");
			OutputPane.OutputString("Adding output assembly to list ...\n");

			NDocAssembly asm = new NDocAssembly(this, ActiveProject);
			ReferenceTracker[ActiveProject.Name] = true;
			Assemblies.Add(asm);
		}

		#endregion

		#region Properties

		private Project m_ActiveProject;
		/// <summary>
		/// The project that is selected for documentation
		/// </summary>
		public Project ActiveProject
		{
			get{return m_ActiveProject;}
		}

		/// <summary>
		/// This is the assembly used by the active project.
		/// <seealso cref="ActiveProject"/>
		/// <seealso cref="Assemblies"/>
		/// <seealso cref="NDocAssembly"/>
		/// </summary>
		/// <remarks>
		/// This is the same as the first <see cref="NDocAssembly"/> element in the <see cref="Assemblies"/> collection.
		/// </remarks>
		public NDocAssembly ActiveProjectAssembly
		{
			get{return Assemblies[0];}
		}

		/// <summary>
		/// The pane we use for text output
		/// </summary>
		public OutputWindowPane OutputPane
		{
			get{return Builder.Current.OutputPane;}
		}

		private NDocAssemblyList m_Assemblies = new NDocAssemblyList();
		/// <summary>
		/// The assemblies that include this project
		/// </summary>
		public NDocAssemblyList Assemblies
		{
			get{return m_Assemblies;}
			set{m_Assemblies=value;}
		}

		/// <summary>
		/// Folder containing the NDoc project file
		/// <seealso cref="CreateNDocProjectFile"/>
		/// </summary>
		public string NDocProjectFolder
		{
			get
			{
				NDocAssembly activeAsm = Assemblies[0];
				return Path.Combine(activeAsm.ProjectPath, "Documentation");
			}
		}

		/// <summary>
		/// Full path to the NDoc project file
		/// <seealso cref="CreateNDocProjectFile"/>
		/// </summary>
		public string NDocProjectFilename
		{
			get
			{
				NDocAssembly activeAsm = Assemblies[0];
				return Path.Combine(NDocProjectFolder, activeAsm.Namespace + ".ndoc");
			}
		}

		/// <summary>
		/// Output folder for the compiled html file
		/// <seealso cref="CompileDocumentation"/>
		/// </summary>
		public string ChmOutputFolder
		{
			get{return Path.Combine(NDocProjectFolder, "Chm");}
		}

		/// <summary>
		/// Full path of compiled html output file
		/// <seealso cref="CompileDocumentation"/>
		/// </summary>
		public string ChmOutputFile
		{
			get{return Path.Combine(ChmOutputFolder, Assemblies[0].Namespace + ".chm");}
		}

		private Hashtable m_hshReferenceTracker	= new Hashtable();
		/// <summary>
		/// Internal hashtable to keep track of references
		/// </summary>
		private Hashtable ReferenceTracker
		{
			get{return m_hshReferenceTracker;}
			set{m_hshReferenceTracker=value;}
		}

		#endregion

		/// <summary>
		/// Adds referenced assemblies to the current project.
		/// <seealso cref="NDocAssembly"/>
		/// <seealso cref="Assemblies"/>
		/// </summary>
		/// <param name="project">The project to check for references</param>
		protected virtual void AddReferences(EnvDTE.Project project)
		{
			try
			{
				OutputPane.OutputString("\nChecking project \"" + project.Name + "\" for references ...\n");

				XmlDocument xmlDoc = new XmlDocument();

				xmlDoc.Load(project.FileName);

				XmlNodeList xmlRefList = xmlDoc.SelectNodes("/VisualStudioProject/CSHARP/Build/References/Reference[@Project]");

				for(int i = 0; i < xmlRefList.Count; i++)
				{
					XmlNode xmlReference = xmlRefList[i];

					string strReferenceName = xmlReference.Attributes["Name"].Value;
					Project referenceProj	= project.Collection.Item(strReferenceName);
					
					if(referenceProj != null && !ReferenceTracker.Contains(referenceProj.Name))
					{
						ReferenceTracker[referenceProj.Name] = true;

						OutputPane.OutputString("Adding reference \"" + referenceProj.Name + "\" ...\n");
						
						try
						{
							NDocAssembly asm = new NDocAssembly(this, referenceProj);
							Assemblies.Add(asm);
						}
						catch(Exception ex)
						{
							OutputPane.OutputString("Invalid reference: " + ex.ToString() + "\nReference not added!\n");
						}

						AddReferences(referenceProj);
					}
				}

			}
			catch(Exception ex)
			{
				throw(new Exception("While parsing projectfile: " + ex.ToString()));
			}
			
		}

		private void WriteAssemblies(StreamWriter sw)
		{
			sw.WriteLine(new string('\t', 1) + "<assemblies>");

			for(int i = 0; i < Assemblies.Count; i++)
			{
				string strAsmPath = Builder.Current.CreateRelativePath(NDocProjectFolder, Assemblies[i].AssemblyFilename);
				string strXmlPath = Builder.Current.CreateRelativePath(NDocProjectFolder, Assemblies[i].XmlDocFilename);

				OutputPane.OutputString("Adding assembly: \"" + strAsmPath + "\" " +
										"xmldoc: \"" + strXmlPath + "\"\n");

				sw.WriteLine(new string('\t', 2) + 
					"<assembly location=\"" + strAsmPath + "\" documentation=\"" + strXmlPath + "\" />");
			}

			sw.WriteLine(new string('\t', 1) + "</assemblies>");
		}

		private void WriteProperty(StreamWriter sw, string strName, string strValue, int nTabs)
		{
			OutputPane.OutputString("Adding property: \"" + strName + "\" " +
									"value: \"" + strValue + "\"\n");
										
			sw.WriteLine(new string('\t', nTabs) + "<property name=\"" + strName + "\" value=\"" + strValue + "\" />");
		}

		/// <summary>
		/// This will create the NDocProject file
		/// </summary>
		/// <param name="sw">The StreamWriter to write to</param>
		private void WriteDocumenterSettings(StreamWriter sw)
		{
			NDocBuilderConfig config = Builder.Current.Configuration;

			sw.WriteLine(new string('\t', 1) + "<documenters>");
			sw.WriteLine(new string('\t', 2) + "<documenter name=\"MSDN\">");
			
			// Loop through all properties and write them out

			for(int i = 0; i < config.DocumenterProperties.Count; i++)
			{
				string strPropertyName	= config.DocumenterProperties[i].Name;
				string strPropertyValue	= config.DocumenterProperties[i].Value;

				WriteProperty(sw, strPropertyName, strPropertyValue, 3);
			}

			/*WriteProperty(sw, "OutputDirectory", config.DocumenterProperties["OutputDirectory"].Value, 3); 
				//Builder.Current.CreateRelativePath(NDocProjectFolder, ChmOutputFolder), 3); 
			WriteProperty(sw, "HtmlHelpName", Assemblies[0].Namespace, 3);
			WriteProperty(sw, "Title", "Namespace " + Assemblies[0].Namespace, 3);
			WriteProperty(sw, "OutputTarget", "HtmlHelp", 3);
			WriteProperty(sw, "FeedbackEmailAddress", "s.groenewoud@mindef.nl", 3);
			WriteProperty(sw, "UseNamespaceDocSummaries", "True", 3);
			WriteProperty(sw, "CleanIntermediates", "True", 3);*/

			// FilesToInclude is a pipe separated value that contains all files to be included 
			// into the chm compile.
			// To build this list of files we check every assembly for resource directories and include this 
			// in the value.
			
			string strFilesToInclude = "";

			// Loop through all assemblies of the current project (this is the assembly of the selected 
			// project and all the referenced project assemblies)

			for(int i = 0; i < Assemblies.Count; i++)
			{
				NDocAssembly asm = Assemblies[i];

				// Loop through all possible resource directory names and check if they exist.

				for(int k = 0; k < config.ResourceDirectories.Length; k++)
				{
					string strCurrentDir = null;
					
					if(Path.IsPathRooted(config.ResourceDirectories[k]))
						strCurrentDir = config.ResourceDirectories[k];
					else
						strCurrentDir = Path.Combine(asm.ProjectPath, config.ResourceDirectories[k]);

					// If the resource directory exist we add them to the list of files to include in the
					// compilation of the chm file.

					if(Directory.Exists(strCurrentDir))
					{
						strFilesToInclude += Path.Combine(strCurrentDir, "*.*") + "|";

						// Check if there is a file named index.htm in the resource directory.
						// If there is one we set this file as the start page of the chm file

						if(i == 0) // First assembly is the active project assembly
						{
							string strStartPageHtm	= Path.Combine(strCurrentDir, "index.htm");
							string strStartPageHtml	= Path.Combine(strCurrentDir, "index.html");

							string strStartPage = (File.Exists(strStartPageHtm) ? strStartPageHtm :
												  (File.Exists(strStartPageHtml) ? strStartPageHtml : null ));

							if(strStartPage != null)
								WriteProperty(sw, "RootPageFileName", strStartPage, 3);
						}

						break;
					}
				}
			}
			
			if(strFilesToInclude!="")
				WriteProperty(sw, "FilesToInclude", strFilesToInclude, 3);

			
			sw.WriteLine(new string('\t', 2) + "</documenter>");
			sw.WriteLine(new string('\t', 1) + "</documenters>");
		}

		/// <summary>
		/// This will create the NDoc project file
		/// <seealso cref="NDocProjectFilename"/>
		/// </summary>
		/// <remarks>
		/// The filename of the NDoc project file is the name of the active project namespace.
		/// The file is saved in the folder <i>Documentation</i>.
		/// If the file already exists it will be overwritten.<br/><br/>
		/// On success the NDoc project file is added to the solution.
		/// </remarks>
		protected virtual void CreateNDocProjectFile()
		{
			OutputPane.OutputString("\nCreating project file: \"" + Path.GetFileName(NDocProjectFilename) + "\"\n");

			StreamWriter sw = null;

			try
			{
				bool bFileExists = File.Exists(NDocProjectFilename);

				// Only create the file if:
				// 		The file does not exists or ...
				//		... the file does exists but the user confirms to overwrite it

				if(!bFileExists || 
					MessageBox.Show("The file \"" + NDocProjectFilename + "\" already exists.\n" + "Do you wish to overwrite the file?",
					"Overwrite", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
				{
					if(bFileExists)
					{
						FileAttributes fa = File.GetAttributes(NDocProjectFilename);
						
						if((fa & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
						{
							OutputPane.OutputString("\n\nWarning: project file is readonly. If this file is under sourcesafe control you will need to checkin the changes manually.\n\n");
							File.SetAttributes(NDocProjectFilename, fa & (~FileAttributes.ReadOnly));
						}
					}

					Directory.CreateDirectory(NDocProjectFolder);

					sw = File.CreateText(NDocProjectFilename);

					sw.WriteLine("<project SchemaVersion=\"1.3\">");
					
					WriteAssemblies(sw);
					WriteDocumenterSettings(sw);

					sw.WriteLine("</project>");
				}
			}
			catch(Exception ex)
			{
				throw(new Exception("Error while creating the project file \"" + NDocProjectFilename + "\"\n" + ex.ToString()));
			}
			finally
			{
				if(sw!=null)
					sw.Close();
			}

			ActiveProject.ProjectItems.AddFromFile(NDocProjectFilename);
		}

		/// <summary>
		/// This will start the compilation process
		/// <seealso cref="NDocProcess"/>
		/// <seealso cref="NDocProjectFilename"/>
		/// <seealso cref="ChmOutputFolder"/>
		/// <seealso cref="ChmOutputFile"/>
		/// </summary>
		/// <remarks>
		/// The output file is added to the project when it has been build.
		/// </remarks>
		protected virtual void CompileDocumentation()
		{
			NDocProcess p = new NDocProcess(NDocProjectFilename);
			p.Exec();

			OutputPane.OutputString("Adding the chm helpfile to the project ...\n");

			ActiveProject.ProjectItems.AddFromFile(ChmOutputFile);

			OutputPane.OutputString("Done.\n");
		}

		/// <summary>
		/// Main function that will build the project documentation
		/// </summary>
		public virtual void Build()
		{
			Builder.Current.Configuration.CurrentProject = this;

			AddReferences(ActiveProject);
			CreateNDocProjectFile();
			CompileDocumentation();
		}
	}
}
